/**
 * Highcharts JS v4.1.5 (2015-04-13)
 * Highcharts Broken Axis module
 *
 * Author: Stephane Vanraes, Torstein Honsi
 * License: www.highcharts.com/license
 */

/*global HighchartsAdapter*/
(function (H) {

    "use strict";

    var pick = H.pick,
        wrap = H.wrap,
        extend = H.extend,
        fireEvent = HighchartsAdapter.fireEvent,
        Axis = H.Axis,
        Series = H.Series;

    function stripArguments() {
        return Array.prototype.slice.call(arguments, 1);
    }

    extend(Axis.prototype, {
        isInBreak: function (brk, val) {
            var repeat = brk.repeat || Infinity,
                from = brk.from,
                length = brk.to - brk.from,
                test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat));

            if (!brk.inclusive) {
                return (test < length && test !== 0);
            } else {
                return (test <= length);
            }
        },

        isInAnyBreak: function (val, testKeep) {
            // Sanity Check			
            if (!this.options.breaks) {
                return false;
            }

            var breaks = this.options.breaks,
                i = breaks.length,
                inbrk = false,
                keep = false;


            while (i--) {
                if (this.isInBreak(breaks[i], val)) {
                    inbrk = true;
                    if (!keep) {
                        keep = pick(breaks[i].showPoints, this.isXAxis ? false : true);
                    }
                }
            }

            if (inbrk && testKeep) {
                return inbrk && !keep;
            } else {
                return inbrk;
            }
        }
    });

    wrap(Axis.prototype, 'setTickPositions', function (proceed) {
        proceed.apply(this, Array.prototype.slice.call(arguments, 1));

        if (this.options.breaks) {
            var axis = this,
                tickPositions = this.tickPositions,
                info = this.tickPositions.info,
                newPositions = [],
                i;

            if (info && info.totalRange >= axis.closestPointRange) {
                return;
            }

            for (i = 0; i < tickPositions.length; i++) {
                if (!axis.isInAnyBreak(tickPositions[i])) {
                    newPositions.push(tickPositions[i]);
                }
            }

            this.tickPositions = newPositions;
            this.tickPositions.info = info;
        }
    });

    wrap(Axis.prototype, 'init', function (proceed, chart, userOptions) {
        // Force Axis to be not-ordinal when breaks are defined
        if (userOptions.breaks && userOptions.breaks.length) {
            userOptions.ordinal = false;
        }

        proceed.call(this, chart, userOptions);

        if (this.options.breaks) {

            var axis = this;

            axis.doPostTranslate = true;

            this.val2lin = function (val) {
                var nval = val,
                    brk,
                    i;

                for (i = 0; i < axis.breakArray.length; i++) {
                    brk = axis.breakArray[i];
                    if (brk.to <= val) {
                        nval -= (brk.len);
                    } else if (brk.from >= val) {
                        break;
                    } else if (axis.isInBreak(brk, val)) {
                        nval -= (val - brk.from);
                        break;
                    }
                }

                return nval;
            };

            this.lin2val = function (val) {
                var nval = val,
                    brk,
                    i;

                for (i = 0; i < axis.breakArray.length; i++) {
                    brk = axis.breakArray[i];
                    if (brk.from >= nval) {
                        break;
                    } else if (brk.to < nval) {
                        nval += (brk.to - brk.from);
                    } else if (axis.isInBreak(brk, nval)) {
                        nval += (brk.to - brk.from);
                    }
                }

                return nval;
            };

            this.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
                // If trying to set extremes inside a break, extend it to before and after the break ( #3857 )
                while (this.isInAnyBreak(newMin)) {
                    newMin -= this.closestPointRange;
                }
                while (this.isInAnyBreak(newMax)) {
                    newMax -= this.closestPointRange;
                }
                Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
            };

            this.setAxisTranslation = function (saveOld) {
                Axis.prototype.setAxisTranslation.call(this, saveOld);

                var breaks = axis.options.breaks,
                    breakArrayT = [],	// Temporary one
                    breakArray = [],
                    length = 0,
                    inBrk,
                    repeat,
                    brk,
                    min = axis.userMin || axis.min,
                    max = axis.userMax || axis.max,
                    start,
                    i,
                    j;

                // Min & Max Check
                for (i in breaks) {
                    brk = breaks[i];
                    if (axis.isInBreak(brk, min)) {
                        min += (brk.to % brk.repeat) - (min % brk.repeat);
                    }
                    if (axis.isInBreak(brk, max)) {
                        max -= (max % brk.repeat) - (brk.from % brk.repeat);
                    }
                }

                // Construct an array holding all breaks in the axis
                for (i in breaks) {
                    brk = breaks[i];
                    start = brk.from;
                    repeat = brk.repeat || Infinity;

                    while (start - repeat > min) {
                        start -= repeat;
                    }
                    while (start < min) {
                        start += repeat;
                    }

                    for (j = start; j < max; j += repeat) {
                        breakArrayT.push({
                            value: j,
                            move: 'in'
                        });
                        breakArrayT.push({
                            value: j + (brk.to - brk.from),
                            move: 'out',
                            size: brk.breakSize
                        });
                    }
                }

                breakArrayT.sort(function (a, b) {
                    if (a.value === b.value) {
                        return (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1);
                    } else {
                        return a.value - b.value;
                    }
                });

                // Simplify the breaks
                inBrk = 0;
                start = min;

                for (i in breakArrayT) {
                    brk = breakArrayT[i];
                    inBrk += (brk.move === 'in' ? 1 : -1);

                    if (inBrk === 1 && brk.move === 'in') {
                        start = brk.value;
                    }
                    if (inBrk === 0) {
                        breakArray.push({
                            from: start,
                            to: brk.value,
                            len: brk.value - start - (brk.size || 0)
                        });
                        length += brk.value - start - (brk.size || 0);
                    }
                }

                axis.breakArray = breakArray;

                fireEvent(axis, 'afterBreaks');

                axis.transA *= ((max - axis.min) / (max - min - length));

                axis.min = min;
                axis.max = max;

            };
        }
    });

    wrap(Series.prototype, 'generatePoints', function (proceed) {

        proceed.apply(this, stripArguments(arguments));

        var series = this,
            xAxis = series.xAxis,
            yAxis = series.yAxis,
            points = series.points,
            point,
            i = points.length;


        if (xAxis && yAxis && (xAxis.options.breaks || yAxis.options.breaks)) {
            while (i--) {
                point = points[i];

                if (xAxis.isInAnyBreak(point.x, true) || yAxis.isInAnyBreak(point.y, true)) {
                    points.splice(i, 1);
                    this.data[i].destroyElements(); // removes the graphics for this point if they exist
                }
            }
        }

    });

    wrap(H.seriesTypes.column.prototype, 'drawPoints', function (proceed) {
        proceed.apply(this);

        var series = this,
            points = series.points,
            yAxis = series.yAxis,
            breaks = yAxis.breakArray || [],
            point,
            brk,
            i,
            j,
            y;

        for (i = 0; i < points.length; i++) {
            point = points[i];
            y = point.stackY || point.y;
            for (j = 0; j < breaks.length; j++) {
                brk = breaks[j];
                if (y < brk.from) {
                    break;
                } else if (y > brk.to) {
                    fireEvent(yAxis, 'pointBreak', {point: point, brk: brk});
                } else {
                    fireEvent(yAxis, 'pointInBreak', {point: point, brk: brk});
                }
            }
        }

    });
}(Highcharts));
